/*******************************************************************************
 * Copyright (c) 2006 CEA List.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     CEA List - initial API and implementation
 *******************************************************************************/
package com.cea.papyrus.core.treeeditpart;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.eclipse.core.runtime.Platform;
import org.eclipse.emf.common.notify.Adapter;
import org.eclipse.emf.common.notify.Notification;
import org.eclipse.emf.common.notify.Notifier;
import org.eclipse.gef.AccessibleEditPart;
import org.eclipse.gef.Request;
import org.eclipse.gef.RootEditPart;
import org.eclipse.gef.commands.Command;
import org.eclipse.gef.commands.CompoundCommand;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.preference.PreferenceConverter;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.widgets.TreeItem;
import org.eclipse.ui.views.properties.IPropertySource;
import org.eclipse.uml2.uml.Behavior;
import org.eclipse.uml2.uml.BehavioredClassifier;
import org.eclipse.uml2.uml.Classifier;
import org.eclipse.uml2.uml.ConnectorEnd;
import org.eclipse.uml2.uml.DirectedRelationship;
import org.eclipse.uml2.uml.Element;
import org.eclipse.uml2.uml.ElementImport;
import org.eclipse.uml2.uml.Event;
import org.eclipse.uml2.uml.Feature;
import org.eclipse.uml2.uml.Generalization;
import org.eclipse.uml2.uml.LiteralSpecification;
import org.eclipse.uml2.uml.Namespace;
import org.eclipse.uml2.uml.Operation;
import org.eclipse.uml2.uml.Package;
import org.eclipse.uml2.uml.PackageImport;
import org.eclipse.uml2.uml.ProfileApplication;
import org.eclipse.uml2.uml.Property;
import org.eclipse.uml2.uml.State;
import org.eclipse.uml2.uml.Transition;
import org.eclipse.uml2.uml.Trigger;

import com.cea.papyrus.core.IPapyrusUIConstants;
import com.cea.papyrus.core.PapyrusPlugin;
import com.cea.papyrus.core.actions.delete.DeleteFromModelAction;
import com.cea.papyrus.core.commands.factories.UMLDi2ElementDeleteFactory;
import com.cea.papyrus.core.editor.ModelManager;
import com.cea.papyrus.core.editpart.IUMLElementEditPart;
import com.cea.papyrus.core.listener.UmlElementListenerForTree;
import com.cea.papyrus.core.preference.PreferenceConstants;
import com.cea.papyrus.core.treeeditpart.pseudoelement.BehaviorFilter;
import com.cea.papyrus.core.treeeditpart.pseudoelement.BehavioredClassifierBehaviorsGroup;
import com.cea.papyrus.core.treeeditpart.pseudoelement.EventsGroup;
import com.cea.papyrus.core.treeeditpart.pseudoelement.ImportsGroup;
import com.cea.papyrus.core.treeeditpart.pseudoelement.PackageBehaviorsGroup;
import com.cea.papyrus.core.treeeditpart.pseudoelement.ProfileApplicationsGroup;
import com.cea.papyrus.core.treeeditpart.pseudoelement.TriggersGroup;
import com.cea.papyrus.core.ui.properties.advanced.EObjectPropertySource;
import com.cea.papyrus.core.utils.CollectionUtils;
import com.cea.papyrus.core.utils.Debug;
import com.cea.papyrus.diagraminterchange2.di2.Diagram;
import com.cea.papyrus.diagraminterchange2.di2.Uml1SemanticModelBridge;



/**
 * this the editpart that represents the UML element in the outline
 */
public class ElementTreeEditPart extends CommonTreeEditPart
implements PropertyChangeListener,  Adapter, IUMLElementEditPart<Element> {

	/**
	 * the UML element
	 */
	protected Element element=null;

	/**
	 * the channel to debug ;-)
	 */
	protected final int channel=20;

	/**
	 * a listener to listen the model
	 */
	protected Adapter _adapter = new UmlElementListenerForTree(this);

	/**
	 * the property source in order to display in the property tab
	 */
	protected IPropertySource propertySource = null;

	/**
	 * 
	 */
	private Notifier target;

	/** maximum number of imports before grouping them in the outline */ 
	protected final int MAX_IMPORT_GROUP_NUMBER = 1;

	/** maximum number of profile application before grouping them in the outline */ 
	protected final int MAX_PROFILE_APPLICATION_GROUP_NUMBER = 1;

	/** maximum number of triggers before grouping them in the outline */ 
	protected final int MAX_TRIGGER_GROUP_NUMBER = 1;

	/** maximum number of events before grouping them in the outline */ 
	protected final int MAX_EVENT_GROUP_NUMBER = 1;

	/** maximum number of behaviors before grouping them in the outline */ 
	protected final int MAX_BEHAVIOR_GROUP_NUMBER = 1;

	/**
	 * Create a new instance of this edit part using the given model element.
	 * 
	 * @param element a non-null ShapesDiagram instance
	 * @param modelmanager 
	 */
	public ElementTreeEditPart(Object element, ModelManager modelmanager) {
		super(element, modelmanager);
		this.element=(Element) element;
		// SetModelTreeEditPart should be called only for the first package found
		// (a Model or a Profile)
		if (modelmanager.getModelTreeEditPart() == null) {
			modelmanager.setModelTreeEditPart(this);
		}

		Debug.debug(this,"ElementEditPartCreate!"+element, channel);
	}

	/**
	 * {@inheritDoc}
	 */
	public RootEditPart getRoot() {
		if (getParent()==null){
			return null;}
		else {
			return getParent().getRoot();}
	}
	/**
	 * {@inheritDoc}
	 */
	public void activate() {
		super.activate();
		// Upon activation, attach to the model element as a property change listener.
		((Notifier) getUmlElement()).eAdapters().add(_adapter);
		if(getUmlElement().getOwner()!=null ){
			((Notifier) getUmlElement().getOwner()).eAdapters().add(_adapter);}
		if(getUmlElement()instanceof Classifier ){
			Iterator<Generalization> itegen=
				((Classifier) getUmlElement()).getGeneralizations().iterator();
			while(itegen.hasNext()){
				Generalization gen= itegen.next();
				((Notifier) gen).eAdapters().add(_adapter);
				((Notifier) gen.getGeneral()).eAdapters().add(_adapter);}
		}
	}

	/**
	 * {@inheritDoc}
	 */
	public void deactivate() {
		((Notifier) getUmlElement()).eAdapters().remove(_adapter);
		if(getUmlElement().getOwner()!=null ){
			((Notifier) getUmlElement().getOwner()).eAdapters().remove(_adapter);}
		if(getUmlElement()instanceof Classifier ){
			Iterator<Generalization> itegen=
				((Classifier) getUmlElement()).getGeneralizations().iterator();
			while(itegen.hasNext()){
				Generalization gen= itegen.next();
				((Notifier) gen).eAdapters().remove(_adapter);
				((Notifier) gen.getGeneral()).eAdapters().remove(_adapter);}
		}
		super.deactivate();
	}

	/**
	 * {@inheritDoc}
	 */
	public String getText() {
		final String text = PapyrusPlugin.UML_SHORT_LABEL_PROVIDER.getText(this);
		return ( text != null) ? text : "";
	}

	/**
	 * {@inheritDoc}
	 */
	public Object getAdapter(Class key) {
		if (IPropertySource.class == key) {
			return getPropertySource();
		}
		else if (AccessibleEditPart.class == key) {
			return getAccessibleEditPart();
		}
		return Platform.getAdapterManager().getAdapter(this, key);
	}

	/**
	 * {@inheritDoc}
	 */
	public org.eclipse.uml2.uml.Element getUmlElement(){
		return element;
	}

	/**
	 * {@inheritDoc}
	 */
	public boolean isElementImport() {

		return  (element instanceof ElementImport);

	}
	/**
	 * {@inheritDoc}
	 */
	public ElementImport getElementImport(){

		if (element instanceof ElementImport){
			return (ElementImport)element;
		}
		return null;	
	}

	/**
	 * {@inheritDoc}
	 */
	public Command getCommand(Request request) {
		org.eclipse.uml2.uml.Element child = getUmlElement();
		CompoundCommand command = new CompoundCommand();
		if(DeleteFromModelAction.DELETE_FROM_MODEL_ACTION_REQUEST.equals(request.getType())) {
			command.add(UMLDi2ElementDeleteFactory.eINSTANCE.createCommand(child));
			return command;
		} 
		return super.getCommand(request);
	}

	/**
	 * used to listen a diagram
	 */
	public void addDiagramListeners(DiagramTreeEditPart diagram) {
		((Notifier) diagram.getDiagram()).eAdapters().add(_adapter);
	}

	/**
	 * @param diagram 
	 * @return <code>true</code> if teh listener was removed
	 */
	public boolean removeDiagramListeners(DiagramTreeEditPart diagram) {
		return ((Notifier) diagram.getDiagram()).eAdapters().remove(_adapter);
	}

	

	/**
	 * {@inheritDoc}
	 */
	protected List getModelChildren() {
		ArrayList contained = new ArrayList();

		// boolean that indicates if sub import should be generated directly
		boolean addImports = true;
		boolean addProfileApplication = true;
		//list of UML subElement
		Element element = getUmlElement();
		Iterator it = element.getOwnedElements().iterator();
		while (it.hasNext()){
			Object elt = it.next();

			// check for element and package imports. If number > MAX_IMPORT_GROUP_NUMBER, create a new SubCollection
			// DirectedRelationship will be added later
			if((!(elt instanceof DirectedRelationship)) 
					&& (!(elt instanceof LiteralSpecification))
					&& (!(elt instanceof ConnectorEnd))
					&& (!(elt instanceof Trigger))
					&& (!(elt instanceof Event))
					&& (!(elt instanceof Behavior))){
				contained.add(elt);
			}
			else if(elt instanceof LiteralSpecification){
				LiteralSpecification lit = (LiteralSpecification) elt;
				if(lit.getNameExpression() != null){
					contained.add(elt);
				}
			} 
		}

		// order the collection according to the given Comparator
		Comparator sorter = modelManager.getOutlineSorter();
		if(sorter!=null) {
			Collections.sort(contained, sorter);
		}

		// check grouping of import elements
		if(getUmlElement() instanceof Namespace) {
			Namespace namespace = ((Namespace)getUmlElement());
			if(hasMoreImportsThan(namespace, MAX_IMPORT_GROUP_NUMBER)) {
				contained.add(new ImportsGroup(namespace)); 
				addImports = false;
			}
		}

		if(getUmlElement() instanceof org.eclipse.uml2.uml.ElementImport) {
			org.eclipse.uml2.uml.ElementImport ei = ((org.eclipse.uml2.uml.ElementImport)getUmlElement());
			if(ei.getImportedElement()!=null  && getRoot()!=null && getViewer()!=null){
				//this lines are put to avoid to have an infinite of recurrence
				Map registry=getViewer().getEditPartRegistry(); 
				Object o = registry.get(ei.getImportedElement());
				if( o==null){
					contained.add(ei.getImportedElement());
				}
			}
		}
		if(getUmlElement() instanceof org.eclipse.uml2.uml.PackageImport) {
			org.eclipse.uml2.uml.PackageImport ei = ((org.eclipse.uml2.uml.PackageImport)getUmlElement());
			if(ei.getImportedPackage()!=null  && getRoot()!=null && getViewer()!=null){
				//this lines are put to avoid to have an infinite of recurrence
				Map registry=getViewer().getEditPartRegistry(); 
				Object o = registry.get(ei.getImportedPackage());
				if( o==null){
					contained.add(ei.getImportedPackage());}
			}
		}

		// check grouping of profile applications
		if(getUmlElement() instanceof org.eclipse.uml2.uml.Package) {
			org.eclipse.uml2.uml.Package package_ = ((org.eclipse.uml2.uml.Package)getUmlElement());
			if(hasMoreProfileApplicationsThan(package_, MAX_PROFILE_APPLICATION_GROUP_NUMBER)) {
				contained.add(new ProfileApplicationsGroup(package_));
				addProfileApplication = false;
			}
		}

		// check grouping of triggers
		if((element instanceof Transition) || (element instanceof State) || (element instanceof BehavioredClassifier)){
			if(hasMoreTriggersThan((Namespace)element, MAX_TRIGGER_GROUP_NUMBER)){
				contained.add(new TriggersGroup((Namespace)element));
			}
			else{
				if(element instanceof Transition)
					contained.addAll(((Transition)element).getTriggers());
				else if(element instanceof State)
					contained.addAll(((State)element).getDeferrableTriggers());
				else if(element instanceof BehavioredClassifier)
					contained.addAll(((BehavioredClassifier)element).getOwnedTriggers());
			}
		}

		//check grouping of events
		if(element instanceof org.eclipse.uml2.uml.Package){
			if(hasMoreEventsThan((Package)element, MAX_EVENT_GROUP_NUMBER)){
				contained.add(new EventsGroup((Package)element));
			}
			else{
				Iterator<Element> iter = ((Package)element).getOwnedElements().iterator();
				while(iter.hasNext()){
					Element elt = (Element)iter.next();
					if(elt instanceof Event)
						contained.add(elt);
				}
			}
		}

		//check grouping of behaviors for behaviored Classifiers
		if(element instanceof BehavioredClassifier){
			if(hasMoreBehaviorsThan((BehavioredClassifier)element, MAX_BEHAVIOR_GROUP_NUMBER)){
				contained.add(new BehavioredClassifierBehaviorsGroup((BehavioredClassifier)element));
			}
			else{
				contained.addAll(((BehavioredClassifier)element).getOwnedBehaviors());
			}
		}
		else{
			if((element instanceof Transition) && (((Transition)element).getEffect() != null))
				contained.add(((Transition)element).getEffect());
			else if(element instanceof State){
				if(((State)element).getDoActivity() != null)
					contained.add(((State)element).getDoActivity());
				if(((State)element).getEntry() != null)
					contained.add(((State)element).getEntry());
				if(((State)element).getExit() != null)
					contained.add(((State)element).getExit());
			}
		}

		// check grouping of behaviors for Packages
		if(element instanceof Package){
			if(hasMoreBehaviorsThan((Package)element, MAX_BEHAVIOR_GROUP_NUMBER)){
				PackageBehaviorsGroup group = new PackageBehaviorsGroup((Package)element) ;
				contained.add(group);
			}
			else{
				Collection<Behavior> behaviors = CollectionUtils.filteredCollection(((Package)element).getPackagedElements(), new BehaviorFilter());
				contained.addAll(behaviors);
			}
		}


		// DirectedRelationship : only show DirectedRelationship if current UML element is its source
		Iterator<DirectedRelationship> itDR = getUmlElement().getSourceDirectedRelationships().iterator();
		while (itDR.hasNext()){
			Object elt = itDR.next();
			// check for element imports: should not add if addImport is set to false (they are grouped in ImportsGroup
			if(elt instanceof PackageImport || elt instanceof ElementImport) {

				if(addImports) {
					contained.add(elt);
				}
			} else if(elt instanceof ProfileApplication) {
				if(addProfileApplication) {
					contained.add(elt);
				}
			} else{
				contained.add(elt);
			}		
		}

		//list of diagrams
		Collection<Diagram> diagramList = modelManager.getDiagrams();
		if (diagramList != null){
			it= diagramList.iterator();
			while(it.hasNext()){
				Diagram currentDiagram = (Diagram)it.next();
				Uml1SemanticModelBridge bridge = (Uml1SemanticModelBridge) currentDiagram.getOwner();
				if (bridge == null) {
					Debug.debug(this, "Warning diagram with null owner...", channel);

				} else {
					if(bridge.getElement().equals(element)) {
						contained.add(currentDiagram);
					}	
				}
			}
		}

		boolean inherited=PapyrusPlugin.getDefault().getPreferenceStore().getBoolean(IPapyrusUIConstants.INHERITED_CONSTANT);
		if (inherited){
			if(getUmlElement() instanceof Classifier){
				Iterator<Property> iteratorFE=((Classifier)(getUmlElement())).getAllAttributes().iterator();
				while(iteratorFE.hasNext()){
					Feature fe= iteratorFE.next();
					if (!(fe.getOwner().equals(getUmlElement()))){
						contained.add(fe);
					}
				}
				Iterator<Operation> iteratorOp=((Classifier)(getUmlElement())).getAllOperations().iterator();
				while(iteratorOp.hasNext()){
					Feature fe= iteratorOp.next();
					if (!(fe.getOwner().equals(getUmlElement()))){
						contained.add(fe);
					}
				}
			}
		}
		return contained;
	}

	/**
	 * {@inheritDoc}
	 */
	protected IPropertySource getPropertySource() {
		if( propertySource == null ) {
			propertySource = new EObjectPropertySource( this.getUmlElement());
		}
		return propertySource;	
	}

	/**
	 * {@inheritDoc}
	 */
	public Notifier getTarget() {
		return target;
	}

	/**
	 * {@inheritDoc}
	 */
	public void notifyChanged(Notification notification) {
		refresh();
	}
	/**
	 * {@inheritDoc}
	 */
	protected void refreshVisuals() {
		super.refreshVisuals();

		// this code is used to detect inherited element and to display into another color.
		if(getUmlElement() instanceof Feature && //
				getParent()!=null){			// in the case of drag and drop
			if(getUmlElement().getOwner()!=(((ElementTreeEditPart)getParent()).getUmlElement())){
				//look for preferences page for color
				//Get the preference store
				IPreferenceStore store = PapyrusPlugin.getDefault().getPreferenceStore();

				((TreeItem)this.getWidget()).setForeground(getPreferenceInheritedElementColor(store));}
		}
	}

	/**
	 * {@inheritDoc}
	 */
	public boolean isAdapterForType(Object type) {
		return type.equals( getModel().getClass() );
	}

	/**
	 * {@inheritDoc}
	 */
	public void setTarget(Notifier newTarget) {
		target = newTarget;
	}

	/**
	 * Returns <code>true</code> if the element has several imports (package and/or elements)
	 * @param namespace the namespace to check
	 * @return <code>true</code> if the element has several imports (package and/or elements)
	 */
	protected boolean hasMoreImportsThan(Namespace namespace, int number) {
		return ((namespace.getElementImports().size()+namespace.getPackageImports().size())>number);
	}

	/**
	 * Returns <code>true</code> if the element has several imports (package and/or elements)
	 * @param namespace the namespace to check
	 * @return <code>true</code> if the element has several imports (package and/or elements)
	 */
	protected boolean hasMoreProfileApplicationsThan(org.eclipse.uml2.uml.Package package_, int number) {
		return (package_.getProfileApplications().size()>number);
	}

	/**
	 * Returns <code>true</code> if the element has several triggers
	 * @param namespace the Namespace to check
	 * @return <code>true</code> if the element has several triggers
	 */
	protected boolean hasMoreTriggersThan(Namespace namespace, int number){
		if(namespace instanceof Transition)
			return (((Transition)namespace).getTriggers().size()>number);
		if(namespace instanceof State)
			return (((State)namespace).getDeferrableTriggers().size()>number);
		if(namespace instanceof BehavioredClassifier)
			return (((BehavioredClassifier)namespace).getOwnedTriggers().size()>number);
		return false;
	}

	/**
	 * Returns <code>true</code> if the element has several events
	 * @param package the Package to check
	 * @return <code>true</code> if the element has several events
	 */
	protected boolean hasMoreEventsThan(org.eclipse.uml2.uml.Package package_, int number){
		int n = 0;
		Iterator<Element> iter = package_.getOwnedElements().iterator();
		while((n<=number) && iter.hasNext()){
			Element elt = (Element)iter.next();
			if(elt instanceof Event)
				n++;
		}
		return (n>number);
	}

	/**
	 * Returns <code>true</code> if the element has several behaviors
	 * @param behavioredClassifier the BehavioredClassifier to check
	 * @return <code>true</code> if the element has several behaviors
	 */
	protected boolean hasMoreBehaviorsThan(BehavioredClassifier behavioredClassifier, int number) {
		return (behavioredClassifier.getOwnedBehaviors().size()>number);
	}

	/**
	 * Returns <code>true</code> if the element has several behaviors
	 * @param package_ the Package to check
	 * @return <code>true</code> if the element has several behaviors
	 */
	protected boolean hasMoreBehaviorsThan(Package package_, int number) {
		// filter packaged elements in package, returns only the instances of Behavior
		return (CollectionUtils.filteredCollection(((Package)package_).getPackagedElements(), new BehaviorFilter()).size()>number);
	}

	/**
	 * This method is used to look for the preferred color of inherited element
	 * @param store {@link IPreferenceStore}
	 * @return the color of the element
	 */
	public static Color getPreferenceInheritedElementColor(IPreferenceStore store) {
		return PapyrusPlugin.colorManager.get(PreferenceConverter.getColor(store, PreferenceConstants.P_INHERITED_ELEMENT_COLOR));
	}

	/**
	 * {@inheritDoc}
	 */
	public void propertyChange(PropertyChangeEvent arg0) {
		//nothing
	}


}

